/* === LICENSE INFO ===
 * This source file is subject to the GPLv3 license that is bundled with this
 * package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl.txt
 * === end of LICENSE INFO === */

/*==============================================================================
  File Name   : main.cpp
  Description : Test driver for GLog module of gUtil.

  Created on  : 2009-07-24
  Updated on  : 2011-01-30
  Updated by  : Lee Shiou Ming

  Cautions    : (important notes)
  ============================================================================*/

//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include "GLog/GLog.hpp"
#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <stdexcept>
#include <cassert>

using namespace gUtil::GLog;


//------------------------------------------------------------------------------
//-- TYPEDEF / STRUCT / UNION / ENUM
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
//-- GLOBAL
//------------------------------------------------------------------------------
const std::string g_AppName       = "Test driver";
const std::string g_AppVersion    = "1.0.10.27";
const std::string g_ModuleName    = "GLog module";
const std::string g_ModuleVersion = "1.0.14.34";


//------------------------------------------------------------------------------
//-- FUNCTION PROTOTYPES
//------------------------------------------------------------------------------
void testNoConfig();
void testNoConfigNoDirectRef();
void testConfigAfterGetInstance();
void testConfigBeforeGetInstance();
void testConfigDefaultFromMT();
void testConfigCustomFromMT();
void testLoadXmlCfgFile();
void testLoadXmlCfgFileAfterAppConfig();
void testLogFileRotation();

// Helper functions
void* testConfigDefaultFromMTHelper(int _threadNum, std::string _logMessage);
void* testConfigCustomFromMTHelper(int _threadNum, std::string _logMessage);


//------------------------------------------------------------------------------
//-- MAIN FUNCTION
//------------------------------------------------------------------------------
int main(int _argc, char* _argv[])
{
    std::cout << std::endl << "======" << std::endl;
    std::cout << g_AppName.c_str() << ", version " << g_AppVersion.c_str();
    std::cout << std::endl << "for " << g_ModuleName.c_str() <<
        ", version " << g_ModuleVersion.c_str();
    std::cout << std::endl << "======" << std::endl << std::endl;


    try
    {
        //testLoadXmlCfgFile();
        //testLoadXmlCfgFileAfterAppConfig();
        testLogFileRotation();
    }
    catch (std::exception& e)
    {
        std::cerr << "[EMERGENCY] main(): " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << "[EMERGENCY] main(): Unknown exception detected." <<
                std::endl;
    }


    //-- Stop the console screen before quit for displaying the output.
    std::cout << std::endl << std::endl << "Press <ENTER> key to exit";
    std::cin.get();

    return 0;
}


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
// Test description:
// 1. Get a logger reference variable via GLog::getInstance(), and start logging
//    through the variable, without configuring the logger instance.
// Expected behavior:
// 1. Logger will acts according to default setting, with LEVEL_NOTICE as max
//    log level, and OUTPUT_SCREEN as the only log output.
void testNoConfig()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    GLog& logger = GLog::getInstance();

    assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
    assert(!logger.append(testLogMsg, LEVEL_INFO));
    assert(!logger.append(testLogMsg, LEVEL_DEBUG));
    assert(logger.append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Start logging directly through GLog::getInstance().append(), without
//    holding variable that references to logger instance, and without
//    configuring the logger instance.
// Expected behavior:
// 1. Logger will acts according to default setting, with LEVEL_NOTICE as max
//    log level, and OUTPUT_SCREEN as the only log output.
void testNoConfigNoDirectRef()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    // Straight away start logging without obtaining explicit logger reference
    // nor configure it.
    assert(GLog::getInstance().append(testLogMsg, LEVEL_EMERGENCY));
    assert(!GLog::getInstance().append(testLogMsg, LEVEL_INFO));
    assert(!GLog::getInstance().append(testLogMsg, LEVEL_DEBUG));
    assert(GLog::getInstance().append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Get a logger reference variable via GLog::getInstance(). Then configure
//    the logger instance, and start logging through the variable.
// Expected behavior:
// 1. Logger will acts according to default setting, with LEVEL_NOTICE as max
//    log level, and OUTPUT_SCREEN as the only log output.
// 2. Custom setting that has been configured after calling getInstance() will
//    be discarded, and the calls to any of those config interfaces will receive
//    false as return value.
void testConfigAfterGetInstance()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    GLog& logger = GLog::getInstance();

    assert(!logger.setLogFilePath("GLogTestDriver"));
    assert(!logger.setMaxLogLevel(LEVEL_ERROR));
    assert(!logger.addOutputType(OUTPUT_FILE));
    assert(!logger.initConfig());
    assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
    assert(!logger.append(testLogMsg, LEVEL_INFO));
    assert(!logger.append(testLogMsg, LEVEL_DEBUG));
    assert(logger.append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Configure the logger instance via GLog's static interfaces. Then get a
//    logger reference variable via GLog::getInstance(), and start logging
//    through that variable.
// Expected behavior:
// 1. Custom setting that has been configured (LEVEL_ERROR as max level,
//    OUTPUT_FILE as the only log output) will take effect and overwriting the
//    default setting.
void testConfigBeforeGetInstance()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    assert(GLog::setLogFilePath("tester_GLog"));
    assert(GLog::setMaxLogLevel(LEVEL_ERROR));
    assert(GLog::addOutputType(OUTPUT_FILE));
    assert(GLog::initConfig());

    GLog& logger = GLog::getInstance();

    assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
    assert(!logger.append(testLogMsg, LEVEL_INFO));
    assert(!logger.append(testLogMsg, LEVEL_DEBUG));
    assert(!logger.append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Get logger reference from multiple threads without configure it in prior.
//    Each thread uses the obtained logger reference to log message.
// Expected behavior:
// 1. Logger will be configured for once only, using the default setting.
void testConfigDefaultFromMT()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";
    int threadNum = 0;

    ++threadNum;
    std::cout << unitTestIDStr << ":Starting thread " << threadNum << std::endl;
    boost::thread thread1(boost::bind(testConfigDefaultFromMTHelper, threadNum,
            testLogMsg));

    ++threadNum;
    std::cout << unitTestIDStr << ":Starting thread " << threadNum << std::endl;
    boost::thread thread2(boost::bind(testConfigDefaultFromMTHelper, threadNum,
            testLogMsg));

    thread1.join();
    thread2.join();
}

void* testConfigDefaultFromMTHelper(int _threadNum, std::string _logMessage)
{
    std::cout << "Thread[" << _threadNum << "] started." << std::endl;

    try
    {
        GLog& logger = GLog::getInstance();
        assert(logger.append(_logMessage, LEVEL_CRITICAL));
    }
    catch (std::exception& e)
    {
        std::cerr << __FUNCTION__ << "(): " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << __FUNCTION__ << "(): Unknown exception detected." <<
                std::endl;
    }

    std::cout << "Thread[" << _threadNum << "] exited." << std::endl;
    return reinterpret_cast<void*>(1);
}


// Test description:
// 1. Explicitly configure logger from multiple threads. Then each thread gets
//    a logger reference and start to log message.
// Expected behavior:
// 1. Logger will be configured for once only, with custom setting from the
//    thread who obtains the config access lock first.
void testConfigCustomFromMT()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";
    int threadNum = 0;

    ++threadNum;
    std::cout << unitTestIDStr << ":Starting thread " << threadNum << std::endl;
    boost::thread thread1(boost::bind(testConfigCustomFromMTHelper, threadNum,
            testLogMsg));

    ++threadNum;
    std::cout << unitTestIDStr << ":Starting thread " << threadNum << std::endl;
    boost::thread thread2(boost::bind(testConfigCustomFromMTHelper, threadNum,
            testLogMsg));

    thread1.join();
    thread2.join();
}

void* testConfigCustomFromMTHelper(int _threadNum, std::string _logMessage)
{
    //std::cout << "Thread[" << threadNum << "] started." << std::endl;

    try
    {
        //assert(GLog::setMaxLogLevel(LEVEL_NOTICE));
        //assert(GLog::addOutputType(LogWriterManager::OUTPUT_FILE));
        //assert(GLog::addOutputType(LogWriterManager::OUTPUT_SCREEN));
        //assert(GLog::initConfig());
        GLog::setLogFilePath("tester_GLog");
        GLog::setMaxLogLevel(LEVEL_NOTICE);
        GLog::addOutputType(OUTPUT_FILE);
        GLog::addOutputType(OUTPUT_SCREEN);
        GLog::initConfig();

        GLog& logger = GLog::getInstance();

        assert(logger.append(_logMessage, LEVEL_EMERGENCY));
        assert(!logger.append(_logMessage, LEVEL_INFO));
        assert(!logger.append(_logMessage, LEVEL_DEBUG));
        assert(logger.append(_logMessage, LEVEL_NOTICE));
    }
    catch (std::exception& e)
    {
        std::cerr << __FUNCTION__ << "(): " << e.what() << std::endl;
    }
    catch (...)
    {
        std::cerr << __FUNCTION__ << "(): Unknown exception detected." <<
                std::endl;
    }

    //std::cout << "Thread[" << threadNum << "] exited." << std::endl;
    return reinterpret_cast<void*>(1);
}


// Test description:
// 1. Use setting loaded from XML config file.
// Expected behavior:
// 1. Logger will acts according to setting specified in XML config file.
void testLoadXmlCfgFile()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    assert(GLog::loadFromConfigFile("GLogConfig.xml", CONFIG_FILE_XML));

    GLog& logger = GLog::getInstance();

    assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
    assert(!logger.append(testLogMsg, LEVEL_INFO));
    assert(!logger.append(testLogMsg, LEVEL_DEBUG));
    assert(logger.append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Load setting from XML config file after logger initialized by application.
// Expected behavior:
// 1. Logger will acts according to setting specified by application instead.
void testLoadXmlCfgFileAfterAppConfig()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    GLog::setLogFilePath("tester_GLog");
    GLog::setMaxLogLevel(LEVEL_NOTICE);
    GLog::addOutputType(OUTPUT_FILE);
    GLog::addOutputType(OUTPUT_SCREEN);
    GLog::initConfig();

    // loadFromConfigFile() should return false since logger already configured.
    assert(!GLog::loadFromConfigFile("GLogConfig.xml", CONFIG_FILE_XML));

    GLog& logger = GLog::getInstance();

    assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
    assert(!logger.append(testLogMsg, LEVEL_INFO));
    assert(!logger.append(testLogMsg, LEVEL_DEBUG));
    assert(logger.append(testLogMsg, LEVEL_NOTICE));
}


// Test description:
// 1. Initialize the logger with log rotation enabled (2KB), then do some
//    logging for hitting the rotation limit.
// Expected behavior:
// 1. Current log file will be renamed, new log file will be generated.
void testLogFileRotation()
{
    std::string unitTestIDStr(__FUNCTION__);
    std::string testLogMsg = unitTestIDStr + ": Test log message";

    GLog::setLogFilePath("tester_GLog");
    GLog::setMaxLogLevel(LEVEL_NOTICE);
    GLog::addOutputType(OUTPUT_FILE);
    GLog::addOutputType(OUTPUT_SCREEN);
    GLog::setLogRotationSize(2000);  // 2000 bytes
    GLog::setLogRotationInterval(TIME_INTERVAL_HOURLY);
    GLog::initConfig();

    GLog& logger = GLog::getInstance();

    for (int i = 0; i < 20; i++)
    {
        assert(logger.append(testLogMsg, LEVEL_EMERGENCY));
        assert(!logger.append(testLogMsg, LEVEL_INFO));
        assert(!logger.append(testLogMsg, LEVEL_DEBUG));
        assert(logger.append(testLogMsg, LEVEL_NOTICE));
    }
}
